package com.airsystem.pos.bengkel.controller;

import java.awt.Frame;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JOptionPane;
import javax.swing.JTextField;

import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.util.JRLoader;
import net.sf.jasperreports.view.JasperViewer;

import org.apache.log4j.Logger;
import org.apache.poi.ss.formula.eval.NotImplementedException;
import org.hibernate.Session;
import org.hibernate.Transaction;

import com.airsystem.pos.bengkel.component.TableFrame;
import com.airsystem.pos.bengkel.controller.report.EmployeeReportController;
import com.airsystem.pos.bengkel.entity.DetailKendaraan;
import com.airsystem.pos.bengkel.entity.DetailLayanan;
import com.airsystem.pos.bengkel.entity.DetailOrderLayanan;
import com.airsystem.pos.bengkel.entity.DetailPelanggan;
import com.airsystem.pos.bengkel.entity.DetailSparepart;
import com.airsystem.pos.bengkel.entity.OrderLayanan;
import com.airsystem.pos.bengkel.entity.Pelanggan;
import com.airsystem.pos.bengkel.interfaces.ICallback;
import com.airsystem.pos.bengkel.interfaces.IComboState;
import com.airsystem.pos.bengkel.interfaces.ILoader;
import com.airsystem.pos.bengkel.interfaces.IServices;
import com.airsystem.pos.bengkel.interfaces.ISystem;
import com.airsystem.pos.bengkel.interfaces.ITable;
import com.airsystem.pos.bengkel.interfaces.ITransaction;
import com.airsystem.pos.bengkel.model.ServiceOrderModel;
import com.airsystem.pos.bengkel.service.LayananServices;
import com.airsystem.pos.bengkel.service.OrderLayananServices;
import com.airsystem.pos.bengkel.service.PelangganServices;
import com.airsystem.pos.bengkel.service.SparepartServices;
import com.airsystem.pos.bengkel.util.HibernateUtil;
import com.airsystem.pos.bengkel.view.dialog.ServiceOrderDialog;

/**
 * @author Budi Oktaviyan <budi.oktaviyan@icloud.com>
 */
public class ServiceOrderController extends ServiceOrderModel implements ITransaction, ITable, ISystem {
	private static final Logger LOG = Logger.getLogger(ServiceOrderController.class.getSimpleName());

	private static final String ROW_DETAIL_SPAREPART = "%s - %s [%s Buah] [Harga Satuan: %s]";

	private final PelangganServices pelangganServices;
	private final OrderLayananServices orderLayananServices;

	private final IServices<DetailLayanan> layananServices;
	private final IServices<DetailSparepart> sparepartServices;

	private final ILoader<List<Pelanggan>> pelangganLoader;
	private final ILoader<Iterable<DetailLayanan>> layananLoader;
	private final ILoader<Iterable<DetailSparepart>> sparepartLoader;

	private final Executor executor;

	private Pelanggan selectedPelanggan;
	private DetailKendaraan selectedDetailKendaraan;
	private DetailLayanan selectedDetailLayanan;
	private DetailSparepart selectedDetailSparepart;

	private final Map<Integer, Record> containerMap;
	private final Map<String, DetailSparepart> sparepartMap;
	private final Map<Integer, DetailSparepart> sparepartComboMap;
	private final Map<String, Long> sparepartChangesMap;

	private int sparepartRecord;

	private Long ID_JASA;

	private DetailKendaraan recordDetailKendaraan;
	private DetailLayanan recordDetailLayanan;
	private DetailSparepart recordDetailSparepart;

	public ServiceOrderController() {
		super();

		pelangganServices = new PelangganServices();
		orderLayananServices = new OrderLayananServices();
		layananServices = new LayananServices();
		sparepartServices = new SparepartServices();

		pelangganLoader = new PelangganLoader();
		layananLoader = new LayananLoader();
		sparepartLoader = new SparepartLoader();

		containerMap = new HashMap<Integer, Record>();
		sparepartMap = new ConcurrentHashMap<String, DetailSparepart>();
		sparepartComboMap = new ConcurrentHashMap<Integer, DetailSparepart>();
		sparepartChangesMap = new ConcurrentHashMap<String, Long>();

		executor = Executors.newCachedThreadPool();
	}

	/**
	 * UI State
	 */
	public void setup() {
		sparepartUsedField.setEditable(false);

		updateButton.setVisible(false);
		deleteButton.setVisible(false);

		namaCombo.setEnabled(true);
		kendaraanCombo.setEnabled(true);
		submitButton.setEnabled(false);

		tanggalChooser.setDate(new Date());
	}

	public void reset() {
		sparepartUsedField.setText("");
		hargaField.setText(selectedDetailLayanan.getHarga().toBigInteger().toString());
		pembayaranCombo.setSelectedIndex(0);

		if (kendaraanCombo.getSelectedItem() != null
		&& 	serviceCombo.getSelectedItem() != null
		&&  sparepartCombo.getSelectedItem() != null) {
			kendaraanCombo.setSelectedIndex(0);
			serviceCombo.setSelectedIndex(0);
			sparepartCombo.setSelectedIndex(0);
		}
	}

	/**
	 * Database Action
	 */
	public void search() {
		if (kendaraanCombo.getModel().getSize() > 0) {
			kendaraanCombo.setSelectedIndex(0);
		}

		if (serviceCombo.getModel().getSize() > 0) {
			serviceCombo.setSelectedIndex(0);
		}

		if (namaCombo.getModel().getSize() > 0) {
			namaCombo.setSelectedIndex(0);
		}

		if (pembayaranCombo.getModel().getSize() > 0) {
			pembayaranCombo.setSelectedIndex(0);
		}

		setSelected(kendaraanCombo.getSelectedItem());
		setSelected(serviceCombo.getSelectedItem());
		setSelected(sparepartCombo.getSelectedItem());
		setSelected(namaCombo.getSelectedItem());

		if (selectedDetailLayanan == null) {
			hargaField.setText("");
		} else {
			hargaField.setText(selectedDetailLayanan.getHarga().toBigInteger().toString());
		}

		dialogServiceOrder = new ServiceOrderDialog();
		if (dialogServiceOrder.getDataOrderLayanan() != null) {
			reset();
			TableFrame.deleteTable(tableServices, tableModel);

			selectedSearchOrderLayanan = dialogServiceOrder.getDataOrderLayanan();
			selectedPelanggan = selectedSearchOrderLayanan.getOrderLayanan().getDetail_pelanggan().getPelanggan();

			tanggalChooser.setDate(selectedSearchOrderLayanan.getOrderLayanan().getTanggal());
			namaCombo.setSelectedItem(selectedPelanggan.getNama());
			totalField.setText(selectedSearchOrderLayanan.getTotalHarga());

			for (DetailOrderLayanan item : selectedSearchOrderLayanan.getOrderLayanan().getDetail_order_layanan()) {
				Record record = new Record(item.getDetail_layanan().getDetail_kendaraan(),
										   item.getDetail_layanan(),
										  (item.getDetail_sparepart() != null
										&& item.getDetail_sparepart().toString().equals("-- PILIH --")) ? null : item.getDetail_sparepart(), 
										   item.getSparepartTerpakai(),
						  				   item.getMetode_s().replaceAll("_", " "));

				kilometerField.setText(item.getKilometer());
				containerMap.put(tableServices.getRowCount(), record);
				tableModel.addRow(convertToVector(record));
			}

			namaCombo.setEnabled(false);
			kendaraanCombo.setEnabled(false);
			tableServices.setModel(tableModel);
			doCount();
		}
	}

	public void get() {
		throw new UnsupportedOperationException("Not Implemented.");
	}

	public void submit() {
		JTextField dateEditor = ((JTextField) tanggalChooser.getDateEditor().getUiComponent());

		if (dateEditor.getText().isEmpty()) {
			JOptionPane.showMessageDialog(this, "Data kosong!", "Pesan", JOptionPane.WARNING_MESSAGE);

			return;
		}

		if (tableServices.getRowCount() == 0) {
			JOptionPane.showMessageDialog(this, "Data kosong!", "Pesan", JOptionPane.WARNING_MESSAGE);

			return;
		}

		Session session = HibernateUtil.getSessionFactory().getCurrentSession();
		Transaction transaction = session.getTransaction();

		try {
			if (!transaction.isActive()) {
				transaction.begin();
			}

			OrderLayanan orderLayanan = new OrderLayanan();

			if (selectedSearchOrderLayanan != null) {
				orderLayanan.setId(selectedSearchOrderLayanan.getOrderLayanan().getId());

				for (DetailOrderLayanan detail : selectedSearchOrderLayanan.getOrderLayanan().getDetail_order_layanan()) {
					session.delete(detail);

					if (detail.getDetail_sparepart() == null) {
						continue;
					}

					DetailSparepart detailSparepartUpdate = null;
					detail.getDetail_sparepart().setChoice(99);

					Iterable<DetailSparepart> spareparts = sparepartServices.select(detail.getDetail_sparepart());
					for (DetailSparepart sparepart : spareparts) {
						detailSparepartUpdate = sparepart;
						break;
					}

					Long balance = detailSparepartUpdate.getStok() + detail.getSparepartTerpakai();
					detailSparepartUpdate.setStok(balance);
					detail.setKilometer(kilometerField.getText());
					session.saveOrUpdate(detailSparepartUpdate);
				}

				session.saveOrUpdate(orderLayanan);
				transaction.commit();
				orderLayanan.setDetail_pelanggan(selectedSearchOrderLayanan.getOrderLayanan().getDetail_pelanggan());
				ID_JASA = selectedSearchOrderLayanan.getOrderLayanan().getId();
			} else {
				orderLayanan.setDetail_pelanggan(selectedPelanggan.getDetail_pelanggan().iterator().next());
			}

			if (!session.isOpen()) {
				session = HibernateUtil.getSessionFactory().getCurrentSession();
			}

			if (!transaction.isActive()) {
				transaction = session.beginTransaction();
			}

			orderLayanan.setTanggal(tanggalChooser.getDate());
			for (Record record : containerMap.values()) {
				DetailOrderLayanan det = new DetailOrderLayanan();
				det.setOrder_layanan(orderLayanan);
				det.setDetail_layanan(record.detailLayanan);
				det.setDetail_sparepart(record.detailSparepart);
				det.setKilometer(kilometerField.getText());
				det.setSparepartTerpakai(record.usedSparepart);
				det.setMetode(record.metode.replaceAll(" ", "_"));
				orderLayanan.addDetail_order_layanan(det);
			}

			session.saveOrUpdate(orderLayanan);
			ID_JASA = orderLayanan.getId();
			for (DetailOrderLayanan child : orderLayanan.getDetail_order_layanan()) {
				session.save(child);
				DetailSparepart detailSparepartUpdate = null;

				if (child.getDetail_sparepart() == null) {
					continue;
				}

				child.getDetail_sparepart().setChoice(99);
				Iterable<DetailSparepart> spareparts = sparepartServices.select(child.getDetail_sparepart());
				for (DetailSparepart sparepart : spareparts) {
					detailSparepartUpdate = sparepart;

					break;
				}

				Long balance = detailSparepartUpdate.getStok() - child.getSparepartTerpakai();
				detailSparepartUpdate.setStok(balance);
				session.saveOrUpdate(detailSparepartUpdate);
			}

			transaction.commit();
			JOptionPane.showMessageDialog(this, "Transaksi sukses", "Pesan", JOptionPane.INFORMATION_MESSAGE);
			print();
			doCancel();
			load();
		} catch (RuntimeException e) {
			LOG.error(e.getMessage(), e);
			JOptionPane.showMessageDialog(this, "Transaksi gagal!", "Pesan", JOptionPane.ERROR_MESSAGE);

			try {
				transaction.rollback();
			} catch (RuntimeException transactionRuntimeEx) {
				LOG.error("Transaction rollback is " + transactionRuntimeEx.getMessage(), transactionRuntimeEx);
			}
		} finally {
			if (session != null && session.isOpen()) {
				session.close();
			}
		}
	}

	public void delete() {
		throw new NotImplementedException("Not implemented!");
	}

	public void print() {
		try {
			Object[] objects = { "Ya", "Tidak" };
			int ask = JOptionPane.showOptionDialog(ServiceOrderController.this, "Print transaksi?", "Konfirmasi", JOptionPane.YES_NO_OPTION,
					  JOptionPane.QUESTION_MESSAGE, null, objects, objects[0]);

			if (ask == 0) {
				printTransaction();
			}
		} catch (Exception e) {
			LOG.error(e.getMessage(), e);
		}
	}

	public void cancel() {
		if (namaCombo.getSelectedItem() != null) {
			namaCombo.setSelectedIndex(0);
		}

		if (isGridClicked) {
			tableServices.clearSelection();
			row = -1;
			reset();
			isGridClicked = false;
		} else {
			doCancel();
		}
	}

	/**
	 * Grid Action
	 */
	public void init() {
		Record record = containerMap.get(row);
		recordDetailKendaraan = record.detailKendaraan;
		recordDetailLayanan = record.detailLayanan;
		recordDetailSparepart = record.detailSparepart;

		ComboBoxModel kendaraanModel = kendaraanCombo.getModel();
		for (int i = 0; i < kendaraanModel.getSize(); i++) {
			DetailKendaraan detailKendaraan = (DetailKendaraan) kendaraanModel.getElementAt(i);

			if (detailKendaraan.toString().equalsIgnoreCase(recordDetailKendaraan.toString())) {
				ComboBoxModel serviceModel = serviceCombo.getModel();

				for (int j = 0; j < serviceModel.getSize(); j++) {
					DetailLayanan detailLayanan = (DetailLayanan) serviceModel.getElementAt(j);

					if (detailLayanan.toString().equalsIgnoreCase(recordDetailLayanan.toString())) {
						if (recordDetailSparepart == null) {
							sparepartCombo.setSelectedIndex(0);
						} else {
							sparepartCombo.setSelectedItem(recordDetailSparepart.toString());
						}

						serviceCombo.setSelectedIndex(j);
						break;
					}
				}

				kendaraanCombo.setSelectedIndex(i);
				break;
			}
		}

		if (recordDetailSparepart == null) {
			sparepartUsedField.setText("");
			sparepartRecord = -1;
		} else {
			sparepartUsedField.setText(String.valueOf(record.usedSparepart));
			sparepartRecord = record.usedSparepart;
		}

		pembayaranCombo.setSelectedItem(record.metode);
		updateButton.setVisible(true);
		deleteButton.setVisible(true);
		doCount();
	}

	public void add() {
		if (!isFormValid()) {
			JOptionPane.showMessageDialog(this, "Data kosong!", "Pesan", JOptionPane.WARNING_MESSAGE);

			return;
		}

		int totalUsedSparepart = 0;
		String usedSparepartLabel = (selectedDetailSparepart != null) ? new StringBuilder(selectedDetailSparepart.getSparepart().getMerk())
																			.append("*").append(selectedDetailSparepart.getSub_merk()).toString() : null;

		if (usedSparepartLabel != null) {
			for (Record item : containerMap.values()) {
				String existingSparepart = (item.detailSparepart != null) ? new StringBuilder(item.detailSparepart.getSparepart().getMerk())
																				.append("*").append(item.detailSparepart.getSub_merk()).toString() : null;

				if (existingSparepart == null) {
					continue;
				}

				if (!usedSparepartLabel.equals(existingSparepart)) {
					continue;
				}

				totalUsedSparepart += item.usedSparepart;
			}

			StringBuilder selectedDetailSparepartBuilder = new StringBuilder(selectedDetailSparepart.getSparepart().getMerk());
			selectedDetailSparepartBuilder.append("*");
			selectedDetailSparepartBuilder.append(selectedDetailSparepart.getSub_merk());

			DetailSparepart selectedDetailSp = sparepartMap.get(selectedDetailSparepartBuilder.toString());
			long sisaSparepart = selectedDetailSp.getStok();

			if (totalUsedSparepart > sisaSparepart) {
				StringBuilder message = new StringBuilder("Jumlah pembelian sparepart ");
				message.append("(");
				message.append(totalUsedSparepart);
				message.append(" buah)");
				message.append("\n");
				message.append("melebihi sisa stok sparepart ");
				message.append("(");
				message.append(selectedDetailSparepart.getStok());
				message.append(" buah)!");

				JOptionPane.showMessageDialog(this, message.toString(), "Pesan", JOptionPane.WARNING_MESSAGE);
				return;
			}
		}

		Record record = new Record(selectedDetailKendaraan,
								   selectedDetailLayanan,
								   selectedDetailSparepart,
								   sparepartUsedField.getText().isEmpty() ? 0 : Integer.parseInt(sparepartUsedField.getText()),
								   (String) pembayaranCombo.getSelectedItem());

		containerMap.put(tableServices.getRowCount(), record);
		tableModel.addRow(convertToVector(record));
		tableServices.setModel(tableModel);
		submitButton.setEnabled(true);
		namaCombo.setEnabled(false);
		kendaraanCombo.setEnabled(false);
		updateSparepartComboStock(null, record);
		doCount();
		reset();
	}

	public void edit() {
		if (!isFormValid()) {
			JOptionPane.showMessageDialog(this, "Data kosong!", "Pesan", JOptionPane.WARNING_MESSAGE);

			return;
		}

		int totalUsedSparepart = 0;
		String usedSparepartLabel = (sparepartCombo.getSelectedIndex() > 0
								  && selectedDetailSparepart != null) ? selectedDetailSparepart.toString() : null;

		if (usedSparepartLabel != null) {
			for (Record item : containerMap.values()) {
				String existingSparepart = (item.detailSparepart != null) ? item.detailSparepart.toString() : null;

				if (existingSparepart == null) {
					continue;
				}

				if (!usedSparepartLabel.equals(existingSparepart)) {
					continue;
				}

				totalUsedSparepart += item.usedSparepart;
			}

			long sisaSparepart = selectedDetailSparepart.getStok();
			if (totalUsedSparepart > sisaSparepart) {
				StringBuilder message = new StringBuilder("Jumlah pembelian sparepart ");
				message.append("(");
				message.append(totalUsedSparepart);
				message.append(" buah)");
				message.append("\n");
				message.append("melebihi sisa stok sparepart ");
				message.append("(");
				message.append(selectedDetailSparepart.getStok());
				message.append(" buah)!");

				JOptionPane.showMessageDialog(this, message.toString(), "Pesan", JOptionPane.WARNING_MESSAGE);
				return;
			}
		}

		Record record = new Record(selectedDetailKendaraan,
								   selectedDetailLayanan,
								   selectedDetailSparepart,
								   sparepartUsedField.getText().isEmpty() ? 0 : Integer.parseInt(sparepartUsedField.getText()),
								   (String) pembayaranCombo.getSelectedItem());

		String dataSparepart = null;
		if (record.detailSparepart == null) {
			dataSparepart = "-";
		} else {
			dataSparepart = String.format(ROW_DETAIL_SPAREPART,
										  record.detailSparepart.getSparepart().getMerk(),
										  record.detailSparepart.getSub_merk(), record.usedSparepart,
										  record.detailSparepart.getHarga_satuan().intValue());
		}

		tableModel.setValueAt(record.detailKendaraan, row, 0);
		tableModel.setValueAt(dataSparepart, row, 1);
		tableModel.setValueAt(record.detailLayanan.toString(), row, 2);
		tableModel.setValueAt(hargaField.getText().trim(), row, 3);
		tableModel.setValueAt((String) pembayaranCombo.getSelectedItem(), row, 4);
		submitButton.setEnabled(true);
		updateSparepartComboStock(containerMap.get(row), record);
		containerMap.put(row, record);
		doCount();
		reset();
	}

	public void remove() {
		containerMap.remove(row);

		for (int i = 0; i < tableServices.getRowCount(); i++) {
			if (i > row) {
				Record record = containerMap.get(i);
				int curIndex = i - 1;
				containerMap.put(curIndex, record);
				containerMap.remove(i);
			}
		}

		tableModel.removeRow(row);
		doCount();
		reset();
		submitButton.setEnabled(true);

		if (tableModel.getRowCount() == 0) {
			doCancel();
		}
	}

	public void load() {
		sparepartChangesMap.clear();
		comboState = new InitializeState();
		comboState.preparePelanggan();
	}

	private boolean isFormValid() {
		if (selectedDetailKendaraan == null
		||  selectedDetailLayanan == null
		||  kilometerField.getText().isEmpty()) {
			return false;
		}

		if (sparepartCombo.getSelectedItem() != null) {
			if (sparepartCombo.getSelectedIndex() != 0) {
				if (sparepartUsedField.getText().isEmpty()) {
					return false;
				}
			}
		}

		return true;
	}

	private Vector<String> convertToVector(Record record) {
		String dataSparepart = null;

		if (record.detailSparepart == null) {
			dataSparepart = "-";
		} else {
			dataSparepart = String.format(ROW_DETAIL_SPAREPART,
										  record.detailSparepart.getSparepart().getMerk(),
										  record.detailSparepart.getSub_merk(), record.usedSparepart,
										  record.detailSparepart.getHarga_satuan().intValue());
		}

		int usedSparepart = record.usedSparepart;
		BigDecimal hargaLayanan = record.detailLayanan.getHarga();
		BigDecimal hargaSparepartSatuan = record.detailSparepart == null ? new BigDecimal(0) : record.detailSparepart.getHarga_satuan();
		BigDecimal total = hargaSparepartSatuan.multiply(new BigDecimal(usedSparepart)).add(hargaLayanan);

		Vector<String> vector = new Vector<String>();
		vector.add(record.detailKendaraan.toString());
		vector.add(dataSparepart);
		vector.add(record.detailLayanan.toString());
		vector.add(String.valueOf(total.toBigInteger()));
		vector.add(record.metode);

		return vector;
	}

	private void doCount() {
		int index = tableServices.getRowCount();
		BigDecimal grandTotal = new BigDecimal(0);

		for (int i = 0; i < index; i++) {
			Long total = Long.valueOf(tableServices.getValueAt(i, 3).toString());
			grandTotal = grandTotal.add(BigDecimal.valueOf(total));
		}

		totalField.setText(String.valueOf(grandTotal.toBigInteger()));
	}

	private void doCancel() {
		TableFrame.deleteTable(tableServices, tableModel);
		kilometerField.setText("");
		totalField.setText("");
		reset();
		setup();
		selectedSearchOrderLayanan = null;
	}

	private void printTransaction() {
		try {
			List<DetailOrderLayanan> detailOrderLayanans = orderLayananServices.transactionPrint(ID_JASA, tanggalChooser.getDate());

			JRBeanCollectionDataSource beanCollectionDataSource = new JRBeanCollectionDataSource(detailOrderLayanans);

			Map<String, Object> parameter = new HashMap<String, Object>();
			parameter.put("image_path", EmployeeReportController.class.getResourceAsStream("/images/report.png"));
			parameter.put("tanggal", tanggalChooser.getDate());

			InputStream inputStream = EmployeeReportController.class.getResourceAsStream("/reports/PrintServiceOrder.jasper");

			JasperReport jasperReport = (JasperReport) JRLoader.loadObject(inputStream);

			JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameter, beanCollectionDataSource);

			JasperViewer jasperViewer = new JasperViewer(jasperPrint, false);
			jasperViewer.setTitle("Transaksi Penjualan Jasa");
			jasperViewer.setExtendedState(Frame.MAXIMIZED_BOTH);
			jasperViewer.setVisible(true);
		} catch (Exception e) {
			LOG.error(e.getMessage(), e);
			JOptionPane.showMessageDialog(this, "Print transaksi gagal!", "Pesan", JOptionPane.ERROR_MESSAGE);
		}
	}

	private void updateSparepartComboStock(Record previousRecord, Record currentRecord) {
		Long currentUsedStock = 0L;
		Long previousUsedStock = 0L;
		String targetBrand = null;

		if (previousRecord != null && previousRecord.detailSparepart != null) {
			targetBrand = previousRecord.detailSparepart.toString();
		} else {
			if (currentRecord.detailSparepart != null
			&& !currentRecord.detailSparepart.toString().equals("-- PILIH --"))
				targetBrand = currentRecord.detailSparepart.toString();
		}

		if (targetBrand == null) {
			return;
		}

		if (currentRecord != null && currentRecord.detailSparepart != null) {
			currentUsedStock = (long) currentRecord.usedSparepart;
		}

		if (previousRecord != null && previousRecord.detailSparepart != null) {
			previousUsedStock = (long) previousRecord.usedSparepart;
		}

		ComboBoxModel model = sparepartCombo.getModel();
		for (int i = 0; i < model.getSize(); i++) {
			String element = model.getElementAt(i).toString();

			if (!element.equals(targetBrand)) {
				continue;
			}

			for (DetailSparepart detail : sparepartComboMap.values()) {
				if (!detail.toString().equals(targetBrand)) {
					continue;
				}

				Long balance = detail.getStok() + previousUsedStock - currentUsedStock;
				detail.setStok(balance);
				sparepartCombo.removeAllItems();
				sparepartCombo.addItem("-- PILIH --");

				for (DetailSparepart item : sparepartComboMap.values()) {
					sparepartCombo.addItem(item.toString());
				}

				StringBuilder changesSparepartBuilder = new StringBuilder(detail.getSparepart().getMerk());
				changesSparepartBuilder.append(" - ");
				changesSparepartBuilder.append(detail.getSub_merk());
				sparepartChangesMap.put(changesSparepartBuilder.toString(), balance);

				break;
			}

			break;
		}
	}

	/**
	 * Inner Class.
	 * <br>
	 * @author Fanny Irawan Sutawanir (fannyirawans@gmail.com)
	 */
	private final class PelangganLoader implements ILoader<List<Pelanggan>> {
		public void load(ICallback<List<Pelanggan>> callback) {
			List<DetailPelanggan> results = null;
			List<Pelanggan> pelanggans = new ArrayList<Pelanggan>();

			try {
				results = pelangganServices.select();

				for (DetailPelanggan detail : results) {
					Pelanggan customer = null;
					boolean isFound = false;

					for (Pelanggan pelanggan : pelanggans) {
						if (detail.getPelanggan().getId() == pelanggan.getId()) {
							isFound = true;
							customer = pelanggan;

							break;
						}
					}

					if (isFound) {
						customer.getDetail_pelanggan().add(detail);
					} else {
						customer = detail.getPelanggan();
						customer.setDetail_pelanggan(new LinkedHashSet<DetailPelanggan>());
						customer.getDetail_pelanggan().add(detail);
						pelanggans.add(customer);
					}
				}
			} catch (Exception e) {
				LOG.error("Load pelanggan error is " + e.getMessage(), e);
			}

			callback.get(pelanggans);
		}
	}

	private final class LayananLoader implements ILoader<Iterable<DetailLayanan>> {
		public void load(ICallback<Iterable<DetailLayanan>> callback) {
			if (selectedDetailKendaraan == null) {
				callback.get(new LinkedList<DetailLayanan>());

				return;
			}

			Iterable<DetailLayanan> results = null;
			try {
				DetailLayanan det = new DetailLayanan();
				det.setChoice(4);
				det.setDetail_kendaraan(selectedDetailKendaraan);
				results = layananServices.select(det);
			} catch (Exception e) {
				LOG.error(e.getMessage(), e);
			}

			callback.get(results);
		}
	}

	private final class SparepartLoader implements ILoader<Iterable<DetailSparepart>> {
		public void load(ICallback<Iterable<DetailSparepart>> callback) {
			if (selectedDetailLayanan == null) {
				callback.get(new LinkedList<DetailSparepart>());

				return;
			}

			Iterable<DetailSparepart> results = null;
			try {
				DetailSparepart det = new DetailSparepart();
				det.setChoice(99);
				det.setDetail_kendaraan(selectedDetailLayanan.getDetail_kendaraan());
				results = sparepartServices.select(det);
				sparepartMap.clear();

				for (DetailSparepart item : results) {
					StringBuilder sparepartMapBuilder = new StringBuilder(item.getSparepart().getMerk());
					sparepartMapBuilder.append("*");
					sparepartMapBuilder.append(item.getSub_merk());
					sparepartMap.put(sparepartMapBuilder.toString(), item);
				}
			} catch (Exception e) {
				LOG.error(e.getMessage(), e);
			}

			callback.get(results);
		}
	}

	private abstract class BaseComboState implements IComboState {
		protected static final String ERROR_MSG = "Cannot invoked method [%s] in [%s] State.";

		protected static final String ILLEGAL_ARGUMENT_MSG = "State [%s] only receive [%s] instance.";

		public void preparePelanggan() {
			throw new IllegalStateException(getErrorMsg("preparePelanggan", this.getClass()));
		}

		public void prepareKendaraan() {
			throw new IllegalStateException(getErrorMsg("prepareKendaraan", this.getClass()));
		}

		public void prepareNopol() {
			throw new IllegalStateException(getErrorMsg("prepareNopol", this.getClass()));
		}

		public void prepareService() {
			throw new IllegalStateException(getErrorMsg("prepareService", this.getClass()));
		}

		public void prepareSparepart() {
			throw new IllegalStateException(getErrorMsg("prepareSparepart", this.getClass()));
		}

		public void prepareHarga() {
			throw new IllegalStateException(getErrorMsg("prepareHarga", this.getClass()));
		}

		protected int getTotal() {
			int hargaService = 0;
			int hargaSparepart = 0;
			int hargaSparepartTerpakai = 0;

			if (selectedDetailLayanan != null
			&&  selectedDetailLayanan.getHarga() != null) {
				hargaService = selectedDetailLayanan.getHarga().intValue();
			}

			if (selectedDetailSparepart != null
			&&  selectedDetailSparepart.getHarga_satuan() != null) {
				hargaSparepart = selectedDetailSparepart.getHarga_satuan().intValue();
			}

			if (sparepartUsedField.getText() != null
			&& !sparepartUsedField.getText().isEmpty()) {
				hargaSparepartTerpakai = Integer.parseInt(sparepartUsedField.getText());
			}

			int total = hargaService + (hargaSparepartTerpakai * hargaSparepart);
			return total;
		}

		protected <T extends BaseComboState, T2> String getIllegalErrorMsg(Class<T> clazz, Class<T2> clazz2) {
			return String.format(ERROR_MSG, clazz.getSimpleName(), clazz.getSimpleName());
		}

		protected abstract void changeState();

		protected <T, S> ComboBoxModel getComboModel(Iterable<S> results, Class<T> clazz) {
			return new DefaultComboBoxModel(getVector(results, clazz));
		}

		private <S, T> Vector<T> getVector(Iterable<S> sources, Class<T> clazz) {
			Vector<T> vector = new Vector<T>();

			for (S item : sources) {
				vector.add(get(clazz, item));
			}

			return vector;
		}

		private <T, P> T get(Class<T> clazz, P param) {
			if (clazz.equals(PelangganComboItem.class)) {
				if (param instanceof Pelanggan) {
					return (T) new PelangganComboItem(param == null ? null : (Pelanggan) param);
				} else if (param == null) {
					return (T) new PelangganComboItem(null);
				}
			} else if (clazz.equals(DetailKendaraan.class)) {
				if (param instanceof DetailPelanggan) {
					return (T) ((DetailPelanggan) param).getDetail_kendaraan();
				}
			} else if (clazz.equals(DetailLayanan.class)) {
				if (param instanceof DetailLayanan) {
					return (T) param;
				}
			} else if (clazz.equals(DetailSparepart.class)) {
				if (param instanceof DetailSparepart) {
					return (T) param;
				} else if (param == null) {
					return (T) new DetailSparepart();
				}
			}

			return null;
		}

		protected <T extends BaseComboState> String getErrorMsg(String methodName, Class<T> clazz) {
			return String.format(ERROR_MSG, methodName, clazz.getSimpleName());
		}
	}

	private final class InitializeState extends BaseComboState {
		public void preparePelanggan() {
			Runnable runnable = new Runnable() {

				public void run() {
					pelangganLoader.load(new ICallback<List<Pelanggan>>() {

								public void get(List<Pelanggan> results) {
									namaCombo.setModel(getComboModel(results, PelangganComboItem.class));

									if (namaCombo.getSelectedItem() != null) {
										selectedPelanggan = ((PelangganComboItem) namaCombo.getSelectedItem()).getPelanggan();
									}

									changeState();
								}
							});
				}
			};

			executor.execute(runnable);
		}

		protected void changeState() {
			comboState = new PelangganSelectedState();
			comboState.prepareKendaraan();
		}
	}

	protected final class PelangganSelectedState extends BaseComboState {
		public PelangganSelectedState() {
		}

		public void prepareKendaraan() {
			Runnable runnable = new Runnable() {

				public void run() {
					if (selectedPelanggan == null) {
						kendaraanCombo.setModel(new DefaultComboBoxModel(new String[] { "" }));
						selectedDetailKendaraan = null;
					} else {
						kendaraanCombo.setModel(getComboModel(selectedPelanggan.getDetail_pelanggan(), DetailKendaraan.class));
						selectedDetailKendaraan = (DetailKendaraan) kendaraanCombo.getSelectedItem();
					}

					if (recordDetailKendaraan != null) {
						kendaraanCombo.setSelectedItem(recordDetailKendaraan.toString());
						recordDetailKendaraan = null;
					}

					changeState();
				}
			};

			executor.execute(runnable);
		}

		protected void changeState() {
			comboState = new KendaraanSelectedState();
			comboState.prepareNopol();
			comboState.prepareService();
		}
	}

	protected final class KendaraanSelectedState extends BaseComboState {
		public KendaraanSelectedState() {
		}

		public void prepareService() {
			if (selectedDetailKendaraan == null) {
				serviceCombo.setModel(new DefaultComboBoxModel(new String[] { "" }));
				changeState();

				return;
			}

			Runnable runnable = new Runnable() {

				public void run() {
					layananLoader.load(new ICallback<Iterable<DetailLayanan>>() {
								public void get(Iterable<DetailLayanan> results) {
									serviceCombo.setModel(getComboModel(results, DetailLayanan.class));
									selectedDetailLayanan = (DetailLayanan) serviceCombo.getSelectedItem();

									if (recordDetailLayanan != null) {
										serviceCombo.setSelectedItem(recordDetailLayanan.toString());
										recordDetailLayanan = null;
									}

									changeState();
								}
							});
				}
			};

			executor.execute(runnable);
		}

		public void prepareNopol() {
			Runnable runnable = new Runnable() {

				public void run() {
					String nopol = null;

					if (selectedDetailKendaraan == null) {
						nopolField.setText("");
					} else {
						for (DetailPelanggan detailPelanggan : selectedDetailKendaraan.getPelanggan_kendaraan()) {
							if (detailPelanggan.getDetail_kendaraan().getSub_merk().equalsIgnoreCase(selectedDetailKendaraan.getSub_merk())) {
								nopol = detailPelanggan.getNopol();

								break;
							}
						}

						nopolField.setText(nopol);
					}

					if (recordDetailKendaraan != null) {
						nopolField.setText(nopol);
						recordDetailKendaraan = null;
					}

					changeState();
				}
			};

			executor.execute(runnable);
		}

		protected void changeState() {
			comboState = new LayananSelectedState();
			comboState.prepareSparepart();
			comboState.prepareHarga();
		}
	}

	protected final class LayananSelectedState extends BaseComboState {
		public LayananSelectedState() {
		}

		public void prepareSparepart() {
			Runnable runnable = new Runnable() {

				public void run() {
					if (selectedDetailLayanan == null) {
						sparepartCombo.setModel(new DefaultComboBoxModel(new String[] { "" }));
						changeState();

						return;
					}

					sparepartLoader.load(new ICallback<Iterable<DetailSparepart>>() {
								public void get(Iterable<DetailSparepart> results) {
									sparepartComboMap.clear();
									sparepartCombo.removeAllItems();
									int index = 0;
									sparepartCombo.addItem("-- PILIH --");

									for (DetailSparepart item : results) {
										String brand = new StringBuilder(item.getSparepart().getMerk()).append(" - ").append(item.getSub_merk()).toString();

										if (sparepartChangesMap.get(brand) != null) {
											item.setStok(sparepartChangesMap.get(brand));
										}

										sparepartCombo.addItem(item.toString());
										sparepartComboMap.put(index++, item);
									}

									sparepartCombo.setSelectedIndex(0);
									selectedDetailSparepart = null;

									if (recordDetailSparepart == null) {
										sparepartCombo.setSelectedIndex(0);
									} else {
										sparepartCombo.setSelectedItem(recordDetailSparepart.toString());
										sparepartUsedField.setText(String.valueOf(sparepartRecord));
										sparepartRecord = -1;
										recordDetailSparepart = null;
									}

									changeState();
								}
							});
				}
			};

			executor.execute(runnable);
		}

		public void prepareHarga() {
			if (getTotal() != 0) {
				hargaField.setText(String.valueOf(getTotal()));
			} else {
				hargaField.setText("");
			}
		}

		protected void changeState() {
			comboState = new SparepartSelectedState();
			comboState.prepareHarga();
		}
	}

	protected final class SparepartSelectedState extends BaseComboState {
		public SparepartSelectedState() {
		}

		public void prepareHarga() {
			if (getTotal() != 0) {
				hargaField.setText(String.valueOf(getTotal()));
			} else {
				hargaField.setText("");
			}
		}

		protected void changeState() {
			throw new IllegalStateException(getErrorMsg("changeState", SparepartSelectedState.class));
		}
	}

	protected final class SparepartInputState extends BaseComboState {
		public SparepartInputState() {
		}

		public void prepareHarga() {
			hargaField.setText(String.valueOf(getTotal()));
		}

		protected void changeState() {
			throw new IllegalStateException(getErrorMsg("changeState", SparepartInputState.class));
		}
	}

	/**
	 * Inner Class Wrapper.
	 * <br>
	 * @author Fanny Irawan Sutawanir (fannyirawans@gmail.com)
	 */
	public final class PelangganComboItem {
		private final Pelanggan pelanggan;

		public PelangganComboItem(Pelanggan pelanggan) {
			this.pelanggan = pelanggan;
		}

		public String toString() {
			return pelanggan == null ? "-- PILIH --" : pelanggan.getNama();
		}

		public Pelanggan getPelanggan() {
			return pelanggan;
		}
	}

	protected final class Record {
		DetailKendaraan detailKendaraan;
		DetailLayanan detailLayanan;
		DetailSparepart detailSparepart;
		int usedSparepart;
		String metode;

		public Record(DetailKendaraan detailKendaraan, DetailLayanan detailLayanan, DetailSparepart detailSparepart, int usedSparepart, String metode) {
			this.detailKendaraan = detailKendaraan;
			this.detailLayanan = detailLayanan;
			this.detailSparepart = detailSparepart;
			this.usedSparepart = usedSparepart;
			this.metode = metode;
		}
	}

	public void setSelected(Object instance) {
		if (isPelangganComboItemInstance(instance)) {
			selectedPelanggan = ((PelangganComboItem) instance).getPelanggan();

			return;
		}

		if (isDetailKendaraanInstance(instance)) {
			selectedDetailKendaraan = (DetailKendaraan) instance;

			return;
		}

		if (isDetailLayananInstance(instance)) {
			selectedDetailLayanan = (DetailLayanan) instance;

			return;
		}

		if (instance instanceof String || isDetailSparepartInstance(instance)) {
			String selectedItem = (String) instance;

			if (selectedItem.equals("-- PILIH --")) {
				selectedDetailSparepart = null;
				sparepartUsedField.setText("");
				sparepartUsedField.setEditable(false);

				return;
			}

			for (DetailSparepart item : sparepartComboMap.values()) {
				if (selectedItem.equals(item.toString())) {
					selectedDetailSparepart = item;
					sparepartUsedField.setEditable(true);

					return;
				}
			}

			return;
		}
	}

	private boolean isPelangganComboItemInstance(Object instance) {
		return (instance instanceof PelangganComboItem);
	}

	private boolean isDetailKendaraanInstance(Object instance) {
		return instance instanceof DetailKendaraan;
	}

	private boolean isDetailLayananInstance(Object instance) {
		return instance instanceof DetailLayanan;
	}

	private boolean isDetailSparepartInstance(Object instance) {
		return instance instanceof DetailSparepart;
	}
}